package net.aiion.weave.impl.injection;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;

import net.aiion.weave.spi.bytecode.Bytecode;
import net.aiion.weave.spi.injection.ContextualFactory;
import net.aiion.weave.spi.injection.InjectionContext;
import net.aiion.weave.spi.injection.InjectionException;
import net.aiion.weave.spi.injection.InjectionService;
import net.aiion.weave.spi.injection.Injector;
import net.aiion.weave.spi.naming.Context;
import net.aiion.weave.spi.naming.Contextual;
import net.aiion.weave.spi.naming.annotations.DynamicScoped;

public class InjectionServiceImpl implements Contextual, InjectionService
{
    private Context context;

    private Map<Class<?>, ContextualFactory> contextualFactories;

    public InjectionServiceImpl() {
    }

    @Override
    public String[] getNames() {
        return new String[] {
            InjectionService.class.getName(), InjectionServiceImpl.class.getName()
        };
    }

    @Override
    public Class<? extends Annotation> getScope() {
        return DynamicScoped.class;
    }

    @Override
    public Object getValue() {
        return this;
    }

    @Override
    public void onBound(final Context aContext) {
        context = aContext;
    }

    @Override
    public void onUnbound(final Context aContext) {
        context = null;
    }

    @Override
    public Object injectInstance(final Class<?> aClass) {
        final Object contextualInstance;

        ContextualFactory contextualFactory = getContextualFactory(aClass);
        if (contextualFactory == null) {
            contextualInstance = createInstance(aClass);
        } else {
            contextualInstance = contextualFactory.createContextual();
            contextualFactory.prepareContextual(contextualInstance);
        }

        Class<?> superClass = aClass.getSuperclass();
        while (superClass != null) {
            contextualFactory = getContextualFactory(superClass);
            if (contextualFactory != null) {
                contextualFactory.prepareContextual(contextualInstance);
            }
            superClass = superClass.getSuperclass();
        }

        return contextualInstance;
    }

    private Object createInstance(final Class<?> aClass) {
        try {
            return aClass.newInstance();
        } catch (final InstantiationException | IllegalAccessException e) {
            throw new InjectionException(e);
        }
    }

    private ContextualFactory getContextualFactory(final Class<?> aClass) {
        if (contextualFactories == null) {
            contextualFactories = new HashMap<>();
            for (ContextualFactory contextualFactory : context.lookupAll(Bytecode
                .findClassesByInterfaceClass(ContextualFactory.class)))
            {
                contextualFactories.put(contextualFactory.getContextualClass(), contextualFactory);
            }
        }

        return contextualFactories.get(aClass);
    }

    @Override
    public Object injectValue(final Object aInjectable, final String aTargetName, final Class<?> aTargetType,
        final Annotation[] aTargetAnnotations)
    {
        final InjectionContext injectionContext = new InjectionContextImpl(context, context.lookupAll(Bytecode
            .findClassesByInterfaceClass(Injector.class)), aInjectable, aTargetName, aTargetType, aTargetAnnotations);
        return injectionContext.forwardInjection();
    }
}
